/*
 * Copyright 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.android.talkback;

import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.support.v4.os.BuildCompat;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;

import com.android.talkback.keyboard.DefaultKeyComboModel;
import com.android.talkback.keyboard.KeyComboModel;
import com.google.android.marvin.talkback.TalkBackService;

import java.util.HashSet;
import java.util.Set;

/**
 * Activity used to set TalkBack's keyboard shortcut preferences.
 */
public class TalkBackKeyboardShortcutPreferencesActivity extends Activity {

    /**
     * Utility method for announcing text via accessibility event.
     */
    public static void announceText(String text, Context context) {
        AccessibilityManager accessibilityManager =
                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        if (accessibilityManager.isEnabled()) {
            AccessibilityEvent event = AccessibilityEvent.obtain(
                    AccessibilityEvent.TYPE_ANNOUNCEMENT);
            event.setContentDescription(text);
            accessibilityManager.sendAccessibilityEvent(event);
        }
    }

    private static void focusCancelButton(AlertDialog alertDialog) {
        Button cancelButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
        cancelButton.setFocusableInTouchMode(true);
        cancelButton.requestFocus();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setTitle(getString(R.string.title_pref_manage_keyboard_shortcuts));

        ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        TalkBackService talkBackService = TalkBackService.getInstance();
        KeyComboManager keyComboManager = talkBackService == null ?
                KeyComboManager.create(this) : talkBackService.getKeyComboManager();
        TalkBackKeyboardShortcutPreferenceFragment fragment =
                TalkBackKeyboardShortcutPreferenceFragment.createFor(keyComboManager.getKeymap());
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, fragment)
                .commit();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    public static class TalkBackKeyboardShortcutPreferenceFragment extends PreferenceFragment {
        private static final String BUNDLE_KEYMAP = "bundle_keymap";

        private static final boolean IS_IN_ARC = TalkBackService.isInArc();

        private static final int[] HIDDEN_SHORTCUT_KEY_IDS_IN_ARC = {
            R.string.keycombo_shortcut_global_suspend,
            R.string.keycombo_shortcut_global_home,
            R.string.keycombo_shortcut_global_recents,
            R.string.keycombo_shortcut_global_notifications,
            R.string.keycombo_shortcut_navigate_next_window,
            R.string.keycombo_shortcut_navigate_previous_window
        };

        private static final int[] HIDDEN_SHORTCUT_KEY_IDS_IN_NON_ARC = {
            R.string.keycombo_shortcut_open_manage_keyboard_shortcuts,
            R.string.keycombo_shortcut_open_talkback_settings
        };

        public static TalkBackKeyboardShortcutPreferenceFragment createFor(String keymap) {
            TalkBackKeyboardShortcutPreferenceFragment preferenceFragment =
                    new TalkBackKeyboardShortcutPreferenceFragment();

            Bundle bundle = new Bundle();
            bundle.putString(BUNDLE_KEYMAP, keymap);
            preferenceFragment.setArguments(bundle);

            return preferenceFragment;
        }

        private String mKeymap;

        private final OnPreferenceChangeListener mPreferenceChangeListener =
                new OnPreferenceChangeListener() {
                    @Override
                    public boolean onPreferenceChange(Preference preference, Object newValue) {
                        String preferenceKeyForTriggerModifier = getKeyComboManager()
                                .getKeyComboModel().getPreferenceKeyForTriggerModifier();
                        if (preference instanceof KeyboardShortcutDialogPreference &&
                                newValue instanceof Long) {
                            KeyboardShortcutDialogPreference keyBoardPreference =
                                    (KeyboardShortcutDialogPreference) preference;
                            keyBoardPreference.setKeyComboCode((Long) newValue);
                            keyBoardPreference.notifyChanged();
                        } else if (preference.getKey() != null &&
                                   preference.getKey().equals(getString(
                                           R.string.pref_select_keymap_key)) &&
                                   newValue instanceof String) {
                            String newKeymap = (String) newValue;

                            // Do nothing if keymap is the same.
                            if (mKeymap.equals(newKeymap)) {
                                return false;
                            }

                            // Replace preference fragment.
                            TalkBackKeyboardShortcutPreferenceFragment fragment =
                                    TalkBackKeyboardShortcutPreferenceFragment.createFor(newKeymap);
                            getFragmentManager().beginTransaction()
                                    .replace(android.R.id.content, fragment).commit();

                            // Set new key combo model.
                            KeyComboManager keyComboManager = getKeyComboManager();
                            keyComboManager.setKeyComboModel(
                                    keyComboManager.createKeyComboModelFor(newKeymap));

                            // Announce new keymap.
                            announceText(String.format(
                                    getString(R.string.keycombo_menu_announce_active_keymap),
                                    getKeymapName(newKeymap)), getActivity());
                        } else if (preference.getKey() != null &&
                                preference.getKey().equals(preferenceKeyForTriggerModifier) &&
                                newValue instanceof String) {
                            mTriggerModifierToBeSet = (String) newValue;

                            ListPreference listPreference = (ListPreference) preference;
                            if (listPreference.getValue().equals(mTriggerModifierToBeSet)) {
                                return false;
                            }

                            CharSequence[] entries = listPreference.getEntries();
                            CharSequence newTriggerModifier = entries[
                                    listPreference.findIndexOfValue(mTriggerModifierToBeSet)];

                            // Show alert dialog.
                            AlertDialog dialog = new AlertDialog.Builder(getActivity())
                                    .setTitle(R.string
                                            .keycombo_menu_alert_title_trigger_modifier)
                                    .setMessage(getString(R.string
                                                .keycombo_menu_alert_message_trigger_modifier,
                                                newTriggerModifier))
                                    .setPositiveButton(android.R.string.ok,
                                            mChooseTriggerModifierConfirmDialogPositive)
                                    .setNegativeButton(android.R.string.cancel,
                                            mChooseTriggerModifierConfirmDialogNegative)
                                    .show();

                            focusCancelButton(dialog);

                            return false;
                        }

                        return true;
                    }
                };

        private String mTriggerModifierToBeSet;

        private final DialogInterface.OnClickListener mChooseTriggerModifierConfirmDialogPositive =
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                resetKeymap();

                KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel();

                // Update preference.
                String preferenceKeyForTriggerModifier =
                        keyComboModel.getPreferenceKeyForTriggerModifier();
                ListPreference listPreference =
                        (ListPreference) findPreference(preferenceKeyForTriggerModifier);
                listPreference.setValue(mTriggerModifierToBeSet);

                // Update KeyComboModel.
                keyComboModel.notifyTriggerModifierChanged();

                // Update UI.
                Set<String> keySet =
                        getKeyComboManager().getKeyComboModel().getKeyComboCodeMap().keySet();
                for (String key : keySet) {
                    KeyboardShortcutDialogPreference preference =
                            (KeyboardShortcutDialogPreference) findPreference(key);
                    preference.onTriggerModifierChanged();
                }

                // Announce that trigger modifier has changed.
                CharSequence[] entries = listPreference.getEntries();
                CharSequence newTriggerModifier =
                        entries[listPreference.findIndexOfValue(mTriggerModifierToBeSet)];
                announceText(getString(R.string.keycombo_menu_announce_new_trigger_modifier,
                            newTriggerModifier),
                        getActivity());

                mTriggerModifierToBeSet = null;
            }
        };

        private final DialogInterface.OnClickListener mChooseTriggerModifierConfirmDialogNegative =
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                mTriggerModifierToBeSet = null;
            }
        };

        private final Preference.OnPreferenceClickListener mResetKeymapPreferenceClickListener =
                new Preference.OnPreferenceClickListener() {
            @Override
            public boolean onPreferenceClick(Preference preference) {
                // Show confirm dialog.
                AlertDialog dialog = new AlertDialog.Builder(getActivity())
                        .setTitle(getString(R.string.keycombo_menu_reset_keymap))
                        .setMessage(getString(R.string.message_in_reset_keymap_confirm_dialog))
                        .setPositiveButton(R.string.reset_button_in_reset_keymap_confirm_dialog,
                                mResetKeymapConfirmDialogPositive)
                        .setNegativeButton(android.R.string.cancel,
                                mResetKeymapConfirmDialogNegative)
                        .show();

                focusCancelButton(dialog);

                return true;
            }
        };

        private final DialogInterface.OnClickListener mResetKeymapConfirmDialogPositive =
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                resetKeymap();

                dialogInterface.dismiss();

                announceText(getString(R.string.keycombo_menu_announce_reset_keymap), getActivity());
            }
        };

        private void resetKeymap() {
            KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel();

            for (String key : keyComboModel.getKeyComboCodeMap().keySet()) {
                long defaultKeyComboCode = keyComboModel.getDefaultKeyComboCode(key);

                // Do nothing if key combo code is not changed from default one.
                if (defaultKeyComboCode == keyComboModel.getKeyComboCodeForKey(key)) {
                    continue;
                }

                // Save with default key combo code.
                keyComboModel.saveKeyComboCode(key, defaultKeyComboCode);

                // Update UI.
                KeyboardShortcutDialogPreference keyboardShortcutDialogPreference =
                        (KeyboardShortcutDialogPreference) findPreference(key);
                keyboardShortcutDialogPreference.setKeyComboCode(defaultKeyComboCode);
                keyboardShortcutDialogPreference.notifyChanged();
            }
        }

        private final DialogInterface.OnClickListener mResetKeymapConfirmDialogNegative =
                new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                dialogInterface.cancel();
            }
        };

        void performClickOnResetKeymapForTesting() {
            Preference resetKeymapPreference =
                    findPreference(getString(R.string.pref_reset_keymap_key));
            mResetKeymapPreferenceClickListener.onPreferenceClick(resetKeymapPreference);
        }

        private KeyComboManager getKeyComboManager() {
            TalkBackService talkBackService = TalkBackService.getInstance();
            return talkBackService == null ? KeyComboManager.create(getActivity()) :
                    talkBackService.getKeyComboManager();
        }

        private String getKeymapName(String keymap) {
            if (keymap.equals(getString(R.string.classic_keymap_entry_value))) {
                return getString(R.string.value_classic_keymap);
            } else if (keymap.equals(getString(R.string.default_keymap_entry_value))) {
                return getString(R.string.value_default_keymap);
            }
            return null;
        }

        private int getPreferenceResourceId(String keymap) {
            if (keymap.equals(getString(R.string.classic_keymap_entry_value))) {
                return R.xml.key_combo_preferences;
            } else if (keymap.equals(getString(R.string.default_keymap_entry_value))) {
                return R.xml.default_key_combo_preferences;
            }
            return R.xml.key_combo_preferences;
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Set preferences to use device-protected storage.
            if (BuildCompat.isAtLeastN()) {
                getPreferenceManager().setStorageDeviceProtected();
            }

            mKeymap = getArguments().getString(BUNDLE_KEYMAP);
            addPreferencesFromResource(getPreferenceResourceId(mKeymap));

            PreferenceScreen resetKeymapPreferenceScreen =
                    (PreferenceScreen) findPreference(getString(R.string.pref_reset_keymap_key));
            resetKeymapPreferenceScreen.setOnPreferenceClickListener(
                    mResetKeymapPreferenceClickListener);

            // Hide select keymap preference in Arc if current keymap is already set to default
            // keymap.
            if (IS_IN_ARC &&
                    getKeyComboManager().getKeyComboModel() instanceof DefaultKeyComboModel) {
                PreferenceCategory keymapPreferenceCategory = (PreferenceCategory)
                        getPreferenceScreen().findPreference(getString(
                                R.string.pref_keymap_category_key));
                ListPreference keymapListPreference = (ListPreference)
                        keymapPreferenceCategory.findPreference(getString(
                                R.string.pref_select_keymap_key));
                keymapPreferenceCategory.removePreference(keymapListPreference);
            }

            int[] hiddenShortcutKeyIds = IS_IN_ARC ? HIDDEN_SHORTCUT_KEY_IDS_IN_ARC :
                    HIDDEN_SHORTCUT_KEY_IDS_IN_NON_ARC;
            Set<String> hiddenShortcutKeys = new HashSet<>();
            for (int id : hiddenShortcutKeyIds) {
                hiddenShortcutKeys.add(getString(id));
            }

            initPreferenceUIs(getPreferenceScreen(), hiddenShortcutKeys);
        }

        /**
         * Initialize preference UIs.
         * @param root Root element of preference UIs.
         * @param hiddenShortcutKeys Set of shortcut keys which will be made hidden. Note that
         *                           preference is made hidden only when its shortcut is disabled in
         *                           the key combo model.
         */
        private void initPreferenceUIs(PreferenceGroup root, Set<String> hiddenShortcutKeys) {
            if (root == null) {
                return;
            }

            final KeyComboModel keyComboModel = getKeyComboManager().getKeyComboModel();
            final String preferenceKeyForTriggerModifier =
                    keyComboModel.getPreferenceKeyForTriggerModifier();

            for (int i = 0; i < root.getPreferenceCount(); i++) {
                final Preference preference = root.getPreference(i);
                final String key = preference.getKey();

                if (key != null && preference instanceof KeyboardShortcutDialogPreference &&
                        !keyComboModel.getKeyComboCodeMap().containsKey(key)) {
                    // Disable or hide preference of unavailable key combo on this device.
                    if (hiddenShortcutKeys.contains(key)) {
                        root.removePreference(preference);
                        i--;
                    } else {
                        preference.setEnabled(false);
                    }
                } else if (preference instanceof KeyboardShortcutDialogPreference ||
                    (key != null && key.equals(getString(R.string.pref_select_keymap_key))) ||
                    (key != null && key.equals(preferenceKeyForTriggerModifier))) {
                    // Set onPreferenceChangeListener.
                    preference.setOnPreferenceChangeListener(mPreferenceChangeListener);
                } else if (preference instanceof PreferenceGroup) {
                    initPreferenceUIs((PreferenceGroup) preference, hiddenShortcutKeys);
                }
            }
        }
    }
}
